using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;

namespace MetaViewAnisotropicDiffusion
{
	namespace Matrix
	{
		public class IntMatrix : MetaViewAnisotropicDiffusion.Matrix.MyMatrix
		{
			public int[,] data;
			
			public IntMatrix(int xdim, int ydim)
			{
				this.xDim = xdim;
				this.yDim = ydim;
				this.data = new int[xdim,ydim];
			}

			public IntMatrix(int xdim, int ydim, int init)
			{
				this.xDim = xdim;
				this.yDim = ydim;
				this.data = new int[xdim,ydim];
				this.Initialize(init);
			}


			public void Initialize(int init)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] = init;
					}
				}
			}


			public IntMatrix Clone()
			{
				IntMatrix clone = new IntMatrix(this.XDim, this.YDim);
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						clone.data[i,j] = this.data[i,j];
					}
				}
				return clone;
			}

			public void CopyData(IntMatrix output)
			{
				// if the sizes doesn't match throw an exception
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						output.data[i,j] = this.data[i,j];
					}
				}

			}


			public void CopyData(Rect inputRect, IntMatrix output, int outputX, int outputY)
			{
				// check the boundaries of matrices..
				// Check if the rectangles match..
				for(int i=inputRect.startX;i<=inputRect.endX;i++)
				{
					for(int j=inputRect.startY;j<=inputRect.endY;j++)
					{
						int targetX = i + (outputX-inputRect.startX);
						int targetY = j + (outputY-inputRect.startY);

						output.data[targetX,targetY] = this.data[i,j];
					}
				}

			}





			public int Max()
			{
				int max = this.data[0,0];
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						max = this.data[i,j]>max ? this.data[i,j] : max;
					}
				}
				return max;
			}

			public int Min()
			{
				int min = this.data[0,0];
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						min = this.data[i,j]<min ? this.data[i,j] : min;
					}
				}
				return min;
			}


			public int AbsMax()
			{
				int max = Math.Abs(this.data[0,0]);
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						int val = Math.Abs(this.data[i,j]);
						max = val>max ? val : max;
					}
				}
				return max;
			}


			public int AbsMin()
			{
				int min = Math.Abs(this.data[0,0]);
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						int val = Math.Abs(this.data[i,j]);
						min = val<min ? val : min;
					}
				}
				return min;
			}


			public void Abs()
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] = Math.Abs(this.data[i,j]);
					}
				}
			}


			public double Average()
			{
				double average = 0;
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						average += this.data[i,j];
					}
				}
				average /= this.XDim*this.YDim;
				return average;
			}


			public double Std()
			{
				double average = this.Average();
				double std = 0.0;
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						std += (this.data[i,j]-average)*(this.data[i,j]-average);
					}
				}
				std = Math.Sqrt(std/(this.XDim*this.YDim));
				return std;
			}



			/// <summary>
			/// Arithmetic operations on matrix elements.
			/// </summary>

			public static IntMatrix Add(IntMatrix mat1, IntMatrix mat2)
			{
				// Check if the dimensions fit together.
				IntMatrix output = new IntMatrix(mat1.XDim, mat1.YDim);
				for(int i=0;i<mat1.XDim;i++)
				{
					for(int j=0;j<mat1.YDim;j++)
					{
						output.data[i,j] = mat1.data[i,j] + mat2.data[i,j];
					}
				}
				return output;
			}

			public void AddValue(int val)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] += val;
					}
				}
			}

			public void AddMatrix(IntMatrix mat)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] += mat.data[i,j];
					}
				}
			}

			public static IntMatrix Subtract(IntMatrix mat1, IntMatrix mat2)
			{
				// Check if the dimensions fit together.
				IntMatrix output = new IntMatrix(mat1.XDim, mat1.YDim);
				for(int i=0;i<mat1.XDim;i++)
				{
					for(int j=0;j<mat1.YDim;j++)
					{
						output.data[i,j] = mat1.data[i,j] - mat2.data[i,j];
					}
				}
				return output;
			}
			public void SubtractValue(int val)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] -= val;
					}
				}
			}

			public void SubtractMatrix(IntMatrix mat)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] -= mat.data[i,j];
					}
				}
			}


			public static IntMatrix Multiply(IntMatrix mat1, IntMatrix mat2)
			{
				// Check if the dimensions fit together.
				IntMatrix output = new IntMatrix(mat1.XDim, mat1.YDim);
				for(int i=0;i<mat1.XDim;i++)
				{
					for(int j=0;j<mat1.YDim;j++)
					{
						output.data[i,j] = mat1.data[i,j] * mat2.data[i,j];
					}
				}
				return output;
			}
			
			public void MultiplyValue(int val)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] *= val;
					}
				}
			}

			public void MultiplyMatrix(IntMatrix mat)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] *= mat.data[i,j];
					}
				}
			}


			public static IntMatrix Divide(IntMatrix mat1, IntMatrix mat2)
			{
				// Check if the dimensions fit together.
				IntMatrix output = new IntMatrix(mat1.XDim, mat1.YDim);
				for(int i=0;i<mat1.XDim;i++)
				{
					for(int j=0;j<mat1.YDim;j++)
					{
						output.data[i,j] = mat1.data[i,j] / mat2.data[i,j];
					}
				}
				return output;
			}
			
			public void DivideValue(int val)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] /= val;
					}
				}
			}

			public void DivideMatrix(IntMatrix mat)
			{
				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						this.data[i,j] /= mat.data[i,j];
					}
				}
			}
			

//			public static DoubleMatrix VectorialAdd(IntMatrix mat1, IntMatrix mat2)
//			{
//				// Check if the dimensions fit together.
//				DoubleMatrix output = new DoubleMatrix(mat1.XDim, mat1.YDim);
//				for(int i=0;i<mat1.XDim;i++)
//				{
//					for(int j=0;j<mat1.YDim;j++)
//					{
//						output.data[i,j] = Math.Sqrt((double)(mat1.data[i,j]*mat1.data[i,j] + mat2.data[i,j]*mat2.data[i,j]));
//					}
//				}
//				return output;
//			}



			public void ImageScale()
			{
				int max = this.Max();
				int min = this.Min();
				//init matrix to zero if it is a constnt matrix.
				if(max == min)
				{
					this.Initialize(0);
				}
				else
				{
					this.SubtractValue(min);
					this.MultiplyValue(255);
					this.DivideValue(max-min);
				}
			}

			public IntMatrix MatrixShift(int shiftX, int shiftY)
			{
				IntMatrix shifted = new IntMatrix(this.XDim, this.YDim);

				for (int i=shiftX;i<this.XDim;i++)
					for (int j=shiftY;j<this.YDim;j++)
						shifted.data[i-shiftX,j-shiftY] = this.data[i,j];
		
				for (int i=0;i<shiftX;i++)
					for (int j=shiftY;j<this.YDim;j++)
						shifted.data[this.XDim-shiftX+i,j-shiftY] = this.data[i,j];

				for (int i=shiftX;i<this.XDim;i++)
					for (int j=0;j<shiftY;j++)
						shifted.data[i-shiftX,this.YDim-shiftY+j] = this.data[i,j];

				for (int i=0;i<shiftX;i++)
					for (int j=0;j<shiftY;j++)
						shifted.data[this.XDim-shiftX+i,this.YDim-shiftY+j] = this.data[i,j];
			
				return shifted;
			}


			public void ReadRaw(string filename)
			{
				FileStream strm;
				BinaryReader breader;
				try
				{
					strm = new FileStream(filename, FileMode.Open, FileAccess.Read);
					breader = new BinaryReader(strm);
				}
				catch(Exception e)
				{
					Console.WriteLine(e.Message);
					Console.WriteLine("Cannot open" + filename + "for reading");
					return;
				}
				for(int j=0; j<this.yDim; j++)
				{
					for(int i=0; i<this.xDim; i++)
					{
						this.data[i,j] = breader.ReadInt32();
					}
				}
				breader.Close();
				strm.Close();
			
			}

			public void WriteRaw(string filename)
			{
				FileStream strm;
				BinaryWriter bwriter;
				try
				{
					strm = new FileStream(filename, FileMode.Create, FileAccess.Write);
					bwriter = new BinaryWriter(strm);
				}
				catch(Exception e)
				{
					Console.WriteLine(e.Message);
					Console.WriteLine("Cannot open" + filename + "for writing");
					return;
				}
				for(int j=0; j<this.yDim; j++)
				{
					for(int i=0; i<this.xDim; i++)
					{
						bwriter.Write(this.data[i,j]);
					}
				}
				bwriter.Close();
				strm.Close();
			}

			public static IntMatrix ReadImage(string filename)
			{
				Bitmap image = new Bitmap(filename);
				IntMatrix temp = new IntMatrix(image.Width, image.Height);
				for(int i=0;i<temp.XDim;i++)
				{
					for(int j=0;j<temp.YDim;j++)
					{
						temp.data[i,j] = (int)(255*(image.GetPixel(i,j)).GetBrightness());
					}
				}
				return temp;
			
			}

			public static IntMatrix ReadBitmap(Bitmap image)
			{
				IntMatrix temp = new IntMatrix(image.Width, image.Height);
				for(int i=0;i<temp.XDim;i++)
				{
					for(int j=0;j<temp.YDim;j++)
					{
						temp.data[i,j] = (int)(255*(image.GetPixel(i,j)).GetBrightness());
					}
				}
				return temp;
			
			}


			public void WriteImage(string filename)
			{
				Bitmap image = new Bitmap(this.XDim, this.YDim);

				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						image.SetPixel(i, j, Color.FromArgb(this.data[i,j], this.data[i,j], this.data[i,j]));
					}
				}
				
				image.Save(filename);

			}

			public Bitmap WriteBitmap()
			{
				Bitmap image = new Bitmap(this.XDim, this.YDim);

				for(int i=0;i<this.XDim;i++)
				{
					for(int j=0;j<this.YDim;j++)
					{
						image.SetPixel(i, j, Color.FromArgb(this.data[i,j], this.data[i,j], this.data[i,j]));
					}
				}
				
				return image;

			}

			public void WriteImageScaled(string filename)
			{
				IntMatrix tmp = this.Clone();
				tmp.ImageScale();
				tmp.WriteImage(filename);
			}

			public Bitmap WriteBitmapScaled()
			{
				IntMatrix tmp = this.Clone();
				tmp.ImageScale();
				return tmp.WriteBitmap();
			}

			public static implicit operator DoubleMatrix(IntMatrix intMat)
			{
				DoubleMatrix result = new DoubleMatrix(intMat.XDim, intMat.YDim);
				for(int i=0;i<intMat.XDim;i++)
				{
					for(int j=0;j<intMat.YDim;j++)
					{
						result.data[i,j] = intMat.data[i,j];
					}
				}
				return result;

			}

			public static bool CompareMat(IntMatrix mat1, IntMatrix mat2)
			{
				for(int i=0;i<mat1.XDim;i++)
				{
					for(int j=0;j<mat1.YDim;j++)
					{
						if(Math.Abs(mat1.data[i,j] - mat2.data[i,j]) > 1)
						{
							return false;
						}
					}
				}

				return true;
			}


			public static IntMatrix Scale(IntMatrix bI, int targetX, int targetY)
			{
				Bitmap b = bI.WriteBitmap();
				IntMatrix tmpI;
				Bitmap tmp = new Bitmap(targetX, targetY);
				Graphics bmpG = Graphics.FromImage(tmp);
				//bmpG.InterpolationMode = InterpolationMode.High;
				//bmpG.SmoothingMode = SmoothingMode.AntiAlias; 
				Rectangle scalingRect = new Rectangle(0, 0, targetX, targetY);
				bmpG.DrawImage(b, scalingRect);
				bmpG.Dispose();
				tmpI = IntMatrix.ReadBitmap(tmp);

				return tmpI;
			}

			public static IntMatrix Scale(IntMatrix bI, int targetX, int targetY, InterpolationMode ip, SmoothingMode s)
			{
				Bitmap b = bI.WriteBitmap();
				IntMatrix tmpI;
				Bitmap tmp = new Bitmap(targetX, targetY);
				Graphics bmpG = Graphics.FromImage(tmp);
				bmpG.InterpolationMode = ip;
				bmpG.SmoothingMode = s; 
				Rectangle scalingRect = new Rectangle(0, 0, targetX, targetY);
				bmpG.DrawImage(b, scalingRect);
				bmpG.Dispose();
				tmpI = IntMatrix.ReadBitmap(tmp);

				return tmpI;
			}


		}


	}
}
